package com.javaeedev.test.util;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hsqldb.Server;
import org.junit.Before;
import org.junit.BeforeClass;

public class DatabaseFixture {

    private static Server server = null;
    private static final Log log = LogFactory.getLog(DatabaseFixture.class);

    private static final String DATABASE_NAME = "javaeedev";
    private static final String SCHEMA_FILE = "schema.sql";
    private static final List<String> initSqls = new ArrayList<String>();

    public static Object createProxy(final Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
                        return new TransactionCallback() {
                            @Override
                            protected Object doInTransaction() throws Exception {
                                return method.invoke(target, args);
                            }
                        }.execute();
                    }
                }
        );
    }

    @BeforeClass
    public static void startDatabase() throws Exception {
        if(server!=null)
            return;
        log.info("Start database for test...");
        server = new Server();
        server.setDatabaseName(0, DATABASE_NAME);
        server.setDatabasePath(0, "mem:" + DATABASE_NAME);
        server.setSilent(true);
        server.start();
        try {
            Class.forName("org.hsqldb.jdbcDriver");
        }
        catch(ClassNotFoundException cnfe) {
            throw new RuntimeException(cnfe);
        }
        LineNumberReader reader = null;
        try {
            reader = new LineNumberReader(new InputStreamReader(DatabaseFixture.class.getClassLoader().getResourceAsStream(SCHEMA_FILE)));
            for(;;) {
                String line = reader.readLine();
                if(line==null)
                    break;
                line = line.trim().replace(" text ", " varchar(2000) ").replace(" text,", " varchar(2000),");
                if(!line.equals(""))
                    initSqls.add(line);
            }
        }
        catch(IOException e) {
            throw new RuntimeException(e);
        }
        finally {
            if(reader!=null) {
                try { reader.close(); } catch(IOException e) {}
            }
        }
    }

    @Before
    public void initTables() {
        for(String sql : initSqls) {
            executeSQL(sql);
        }
    }

    static Connection getConnection() throws SQLException {
        return DriverManager.getConnection("jdbc:hsqldb:mem:" + DATABASE_NAME, "sa", "");
    }

    static void close(Statement stmt) {
        if(stmt!=null) {
            try {
                stmt.close();
            }
            catch(SQLException e) {}
        }
    }

    static void close(Connection conn) {
        if(conn!=null) {
            try {
                conn.close();
            }
            catch(SQLException e) {}
        }
    }

    static void executeSQL(String sql) {
        Connection conn = null;
        Statement stmt = null;
        try {
            conn = getConnection();
            boolean autoCommit = conn.getAutoCommit();
            conn.setAutoCommit(true);
            stmt = conn.createStatement();
            stmt.execute(sql);
            conn.setAutoCommit(autoCommit);
        }
        catch(SQLException e) {
            log.warn("Execute failed: " + sql + "\nException: " + e.getMessage());
        }
        finally {
            close(stmt);
            close(conn);
        }
    }

}
